package com.blade.common.utils;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Excel文件数据的读写
 */
public class ExcelUtil {
    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 根据文件的地址字符串读取文件内容
     * @param filePath 文件的地址字符串
     * @return 读取出来的excel数据
     * @throws IOException
     */
    public static List<String[]> readExcel(String filePath) throws IOException {
        return readExcel(filePath, 0);
    }

    /**
     * 根据文件流读取文件内容
     * @param file 文件流对象
     * @return 读取出来的数据
     * @throws IOException
     */
    public static List<String[]> readExcel(File file) throws IOException {
        return readExcel(new BufferedInputStream(new FileInputStream(file)), 0);
    }

    /**
     * 根据文件的地址字符串读取文件内容，并且制定从第几行开始读取
     * @param filePath 文件字符串
     * @param rowNum excel的指定的行号
     * @return 读取出来的excel数据
     * @throws IOException
     */
    public static List<String[]> readExcel(String filePath, int rowNum) throws IOException {
        return readExcel(new BufferedInputStream(new FileInputStream(filePath)), rowNum);
    }


    /**
     * 根据文件流读取文件内容，并且制定从第几行开始读取
     * @param file 文件流对象
     * @param rowNum excel的指定的行号
     * @return 读取出来的数据
     * @throws IOException
     */
    public static List<String[]> readExcel(File file, int rowNum) throws IOException {
        return readExcel(new BufferedInputStream(new FileInputStream(file)), rowNum);
    }

    /**
     * @param io Excel文档输入流，读取excel文档内容，返回一个list数组
     * @param rowNum 从第几行开始读数据，一般第一行是标题，所以从第二行开始
     * @return 解析出的数据
     * @throws IOException
     */
    public static List<String[]> readExcel(InputStream io, int rowNum) throws IOException {
        //创建数据簿
        Workbook wb;

        try {
            // 根据文件流的格式自动选择数据簿的格式
            wb = WorkbookFactory.create(io);
        } catch (InvalidFormatException e) {
            throw new RuntimeException(e);
        }

        //默认读取第一张数据表
        Sheet sheet = wb.getSheetAt(0);

        // 获取总行数
        int totalRows = sheet.getPhysicalNumberOfRows();

        // 单元格开始
        int totalCells = 0;

        // 得到第0行
        Row firstRow = sheet.getRow(0);

        // 确保有数据行
        if (totalRows >= 1 && firstRow != null) {
            // 得到一行中单元格的数量
            totalCells = firstRow.getPhysicalNumberOfCells();
        }

        // 存储取出来的excel数据
        List<String[]> dataList = new ArrayList<>();

        //从第rowNum行开始读取数据
        for (int r = rowNum; r < totalRows; r++) {
            Row row = sheet.getRow(r);
            if (row == null) {
                continue;
            }

            String[] rowList = new String[totalCells];

            for (int c = 0; c < totalCells; c++) {
                Cell cell = row.getCell(c);

                if (cell == null) {
                    rowList[c] = "";
                    continue;
                }

                int type = cell.getCellType();

                // 如果单元格的内容是数字，得转一下
                if (type == Cell.CELL_TYPE_NUMERIC) {
                    double num = cell.getNumericCellValue();

                    if (num == (long) num) {
                        rowList[c] = (long) num + "";
                    } else {
                        BigDecimal decimal = new BigDecimal(num + "");
                        rowList[c] = decimal.toString();
                    }

                    continue;
                }

                rowList[c] = cell.toString();
            }

            dataList.add(rowList);
        }
        return dataList;
    }

    /**
     * 导出excel到本地磁盘，这里默认xls文件后缀
     */
    public static <T> void writeExcel(List<T> beanList, String filePath, String sheetName, Map<String, String> colDataMap) throws FileNotFoundException {
        writeExcel(beanList, new FileOutputStream(filePath), sheetName, colDataMap, true);
    }

    /**
     * 导出excel到os指定的输出，这里默认xls文件后缀
     */
    public static <T> void writeExcel(List<T> beanList, OutputStream os, String sheetName, Map<String, String> colDataMap) throws FileNotFoundException {
        writeExcel(beanList, os, sheetName, colDataMap, true);
    }

    /**
     * 根据传入的colDataMap标题规则和对应数据列表beanList，来生成一个excel文件放在指定路径filePath
     * @param beanList    Bean列表，即数据列表
     * @param os          文档输出源，可以控制是下载文件还是保存excel到硬盘路径
     * @param sheetName   数据表名称
     * @param colDataMap  表头文字与Bean属性对应Map
     * @param isExcel2003 文档格式，true为2003版本，false为2007版本及以上，因为2003版本和2007的创建方式是不同的，所以需要区分
     */
    public static <T> void writeExcel(List<T> beanList, OutputStream os, String sheetName, Map<String, String> colDataMap, boolean isExcel2003) {
        //默认创建2013版Excel文档
        Workbook wb = isExcel2003 ? new HSSFWorkbook() : new XSSFWorkbook();
        Sheet sheet = wb.createSheet(sheetName);// 添加sheet

        List<String> keyList = new LinkedList<>();
        List<String> valList = new LinkedList<>();

        for (Map.Entry<String, String> entry : colDataMap.entrySet()) {
            keyList.add(entry.getKey());
            valList.add(entry.getValue());
        }

        //首先写入表格标题行
        writeCol(sheet, valList, 0);

        int rowNum = 1;
        List<String> col;

//        FileOutputStream os = null;

        try {
            // 将结果集写入
            for (T t : beanList) {
                col = new LinkedList(); // 用以保存一行数据

                // 这个keyList就是上面的colDataMap的key，和Bean的属性名对应，后面通过这个反射调用属性对应的get方法
                // 调用本方法设置了几个属性key，就是我们要同步到excel的数据。
                for (String str : keyList) {
                    PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(t.getClass(), str);
                    Method method = descriptor.getReadMethod();

                    Object obj = method.invoke(t);

                    if (obj instanceof Date) {
                        obj = dateFormat.format(obj);
                    }

                    col.add(null == obj ? "" : obj.toString());// 将一行内容保存在col中
                }

                // 写入Excel，前面调用过writeCol是为了写入标题，这里开始写入数据
                writeCol(sheet, col, rowNum++);
            }
            // 导出文件
//            os = new FileOutputStream(filePath);

            wb.write(os);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                os.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 将数组写入工作簿，就是在指定的rowNum行填充单元格内容
     *
     * @param sheet  要写入的工作簿
     * @param col    要写入的数据数组
     * @param rowNum 要写入的行数
     */
    private static void writeCol(Sheet sheet, List<String> col, int rowNum) {
        Row row = sheet.createRow(rowNum);
        for (int i = 0; i < col.size(); i++) { // 写入每一列
            row.createCell(i).setCellValue(col.get(i));
        }
    }
}
